using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Multiplicity.Reflection {

    public static class CompressedInt {

        public const int MaxValue = 0x1fffffff;
        public const int MinValue = 0x0;

        const uint OneByteMax = 0x80;
        const uint TwoByteMax = 0x4000;

        const byte OneByteTag = 0x0;
        const byte TwoByteTag = 0x80;
        const byte FourByteTag = 0xc0;

        const byte OneByteMask = 0x80;
        const byte TwoByteMask = 0xc0;
        const byte FourByteMask = 0xe0;

        public static uint GetByteCount(uint value) {
            if (value < OneByteMax)
                return 1;

            if (value < TwoByteMax)
                return 2;

            return 4;
        }

        public static uint Read(IEnumerable<byte> bytes) {
            using (IEnumerator<byte> b = bytes.GetEnumerator()) {
                return Read(b);
            }
        }

        public static void Write(BinaryWriter bw, uint value) {

            if (value < OneByteMax) {
                bw.Write((byte)(value & ~OneByteMask) | OneByteTag);
            }
            else if (value < TwoByteMax) {
                bw.Write((byte)(((value >> 8) & ~TwoByteMask) | TwoByteTag));
                bw.Write((byte)(value & byte.MaxValue));
            }
            else {
                bw.Write((byte)(((value >> 24) & (FourByteMask - 1)) | FourByteTag));
                bw.Write((byte)((value >> 16) & byte.MaxValue));
                bw.Write((byte)((value >> 8) & byte.MaxValue));
                bw.Write((byte)(value & byte.MaxValue));
            }
        }

        static uint Read(IEnumerator<byte> bytes) {
            uint retval = 0;
            byte st = NextByte(bytes);

            if ((st & OneByteMask) == OneByteTag) {
                retval = (uint)(st & ~OneByteMask);
            }
            else {
                byte nd = NextByte(bytes);

                if ((st & TwoByteMask) == TwoByteTag) {
                    retval = (uint)(((st & ~TwoByteMask) << 8) | (nd));
                }
                else if ((st & FourByteMask) == FourByteTag) {
                    byte rd = NextByte(bytes);
                    byte th = NextByte(bytes);

                    retval = (uint)(((st & ~FourByteMask) << 24) | (nd << 16) | (rd << 8) | (th));
                }
            }

            return retval;
        }

        static byte NextByte(IEnumerator<byte> bytes) {
            if (!bytes.MoveNext()) Throw.ArgumentException("End of stream");

            return bytes.Current;
        }

    }
}
